<template>
  <div class="flex justify-center">
    <div class="w-[309px] py-10 pt-4 px-5">
      <div class="colors-options-wrap flex flex-wrap gap-2.5">
        <div>
          <input type="color" class="w-9 h-9 rounded-full" />
        </div>
        <div v-for="color in colors" :key="color"
          class="w-9 h-9 rounded-full flex items-center justify-center cursor-pointer"
          @click="userSelectedColor = color" :style="{ backgroundColor: color }">
          <div v-show="userSelectedColor === color" class="w-3 h-3 rounded-full" :style="{ backgroundColor: '#fff' }">
          </div>
        </div>
      </div>
      <div class="mt-5">
        <div class="flex items-center gap-2">
          <div @click="userSelectedMode = (userSelectedMode % 3) + 1">点击切换模式</div>
          <div v-show="userSelectedMode === 1">油漆桶</div>
          <div v-show="userSelectedMode === 2">橡皮</div>
          <div v-show="userSelectedMode === 3">画笔</div>
        </div>
      </div>
    </div>
    <div
      class="w-full h-full border flex flex-col items-center border-[#DCDFE6] bg-[#F8F9FB] rounded-md draw-container">
      <canvas ref="canvasRef" id="canvas" width="600" height="600"></canvas>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from "vue";
const cvs = ref<HTMLCanvasElement | null>(null);
const ctx = ref<CanvasRenderingContext2D | null>(null);
const inputColor = ref<HTMLInputElement | null>(null);

// 设置画布的初始尺寸
const canvasWidth = 400;
const canvasHeight = 600;

const colors = ref([
  '#FF0000',
  '#FF7F00',
  '#FFFF00',
  '#00FF00',
  '#0000FF',
  '#4B0082',
  '#9400D3',
  '#FF69B4',
  '#FFC0CB',
  '#FFD700',
  '#32CD32',
  '#87CEEB',
  '#8A2BE2',
  '#FF6347',
  '#00FFFF',
  '#FFFFFF',
  '#000000',
  '#808080',
  '#A52A2A',
  '#F5F5DC',
]);

// 当前选中的颜色
const userSelectedColor = ref('#FF0000');
// 当前绘画模式 0橡皮 1油漆桶 2画笔
const userSelectedMode = ref(1);

// 用于存储当前可绘制区域的像素坐标
const currentDrawableArea = ref(new Set<string>());

onMounted(() => {
  cvs.value = document.getElementById("canvas") as HTMLCanvasElement;
  ctx.value = cvs.value.getContext("2d", {
    willReadFrequently: true,
  });
  inputColor.value = document.querySelector(
    'input[type="color"]'
  ) as HTMLInputElement;

  // 设置画布的初始尺寸
  cvs.value!.width = canvasWidth;
  cvs.value!.height = canvasHeight;

  init();

  // 鼠标状态变量
  let isDrawing = false;
  let lastDrawTime = 0; // 添加时间戳变量用于节流

  // 获取当前设备像素比
  const getDpr = () => window.devicePixelRatio || 1;

  // 鼠标按下事件处理
  cvs.value?.addEventListener("mousedown", (e: MouseEvent) => {
    if (userSelectedMode.value === 2) { // 橡皮模式
      isDrawing = true;
      const dpr = getDpr();
      eraser(e.offsetX * dpr, e.offsetY * dpr);
    } else if (userSelectedMode.value === 1) { // 油漆桶模式
      const dpr = getDpr();
      const clickX = e.offsetX * dpr;
      const clickY = e.offsetY * dpr;
      console.log(clickX, clickY);
      // 获取整个画布的图像数据，而不是单个像素
      const imageData = ctx.value!.getImageData(0, 0, cvs.value!.width, cvs.value!.height);
      const cvsColors = imageData.data;

      // 方案2: 检查周围像素 - 如果是描边，周围应该有多个黑色像素
      let blackNeighborCount = 0;
      for (let dx = -1; dx <= 1; dx++) {
        for (let dy = -1; dy <= 1; dy++) {
          if (dx === 0 && dy === 0) continue;

          const nx = clickX + dx;
          const ny = clickY + dy;

          if (nx >= 0 && nx < cvs.value!.width && ny >= 0 && ny < cvs.value!.height) {
            const neighborIndex = point2Index(nx, ny);
            const nR = cvsColors[neighborIndex];
            const nG = cvsColors[neighborIndex + 1];
            const nB = cvsColors[neighborIndex + 2];

            const nBrightness = (0.299 * nR + 0.587 * nG + 0.114 * nB);
            if (nBrightness < 60) {
              blackNeighborCount++;
            }
          }
        }
      }

      if (blackNeighborCount >= 5) { // 如果周围有多个黑色像素，可能是描边
        console.log("点击了黑色描边或其附近，不进行上色");
        return;
      }

      // 获取用户选择的颜色
      const hex = userSelectedColor.value;
      const targetColor = hex2rgba(hex);

      console.log("hex", targetColor);
      const originalColor = getColorAtPixel(cvsColors, clickX, clickY);
      console.log("originalColor", originalColor);

      changeColor(clickX, clickY, cvsColors, targetColor, originalColor);
      ctx.value!.putImageData(imageData, 0, 0);
    } else if (userSelectedMode.value === 3) { // 画笔模式
      isDrawing = true;
      const dpr = getDpr();
      const clickX = e.offsetX * dpr;
      const clickY = e.offsetY * dpr;
      console.log(clickX, clickY);
      captureStartColor(clickX, clickY);
    }
  });

  // 鼠标移动事件处理
  cvs.value?.addEventListener("mousemove", (e: MouseEvent) => {
    if (isDrawing) {
      const dpr = getDpr();
      if (userSelectedMode.value === 2) {
        eraser(e.offsetX * dpr, e.offsetY * dpr);
      } else if (userSelectedMode.value === 3) {
        const dpr = getDpr();
        const clickX = e.offsetX * dpr;
        const clickY = e.offsetY * dpr;
        
        // 使用节流控制绘制频率，避免过于频繁的重绘
        // 对于画笔功能，每5毫秒最多执行一次，提高流畅度
        if (!lastDrawTime || Date.now() - lastDrawTime > 5) {
          drawWithBrush(clickX, clickY);
          lastDrawTime = Date.now();
        }
      }
    }
  });

  // 鼠标抬起事件处理
  cvs.value?.addEventListener("mouseup", () => {
    isDrawing = false;
  });

  // 鼠标离开画布事件处理
  cvs.value?.addEventListener("mouseleave", () => {
    isDrawing = false;
  });
});

// 橡皮擦函数 - 添加到文件中
function eraser(x: number, y: number) {
  if (!ctx.value) return;

  const eraserSize = 15; // 橡皮擦大小
  const imageData = ctx.value.getImageData(0, 0, cvs.value!.width, cvs.value!.height);
  const data = imageData.data;

  // 在橡皮擦范围内将像素设置为白色
  for (let i = -eraserSize; i <= eraserSize; i++) {
    for (let j = -eraserSize; j <= eraserSize; j++) {
      // 计算当前点到橡皮擦中心的距离
      const distance = Math.sqrt(i * i + j * j);

      // 只擦除圆形区域内的像素
      if (distance <= eraserSize) {
        const currentX = x + i;
        const currentY = y + j;

        // 确保在画布范围内
        if (currentX >= 0 && currentX < cvs.value!.width && currentY >= 0 && currentY < cvs.value!.height) {
          const index = point2Index(currentX, currentY);

          // 使用周围像素检查方法判断是否为描边
          // 检查当前像素周围是否有多个黑色像素，如果有则认为是描边
          let blackNeighborCount = 0;

          // 先检查当前像素本身是否为黑色
          const r = data[index];
          const g = data[index + 1];
          const b = data[index + 2];
          const brightness = (0.299 * r + 0.587 * g + 0.114 * b);

          // 如果当前像素本身就是黑色，则不擦除
          if (brightness < 60) {
            continue;
          }

          // 检查周围像素
          for (let dx = -1; dx <= 1; dx++) {
            for (let dy = -1; dy <= 1; dy++) {
              if (dx === 0 && dy === 0) continue;

              const nx = currentX + dx;
              const ny = currentY + dy;

              if (nx >= 0 && nx < cvs.value!.width && ny >= 0 && ny < cvs.value!.height) {
                const neighborIndex = point2Index(nx, ny);
                const nR = data[neighborIndex];
                const nG = data[neighborIndex + 1];
                const nB = data[neighborIndex + 2];

                const nBrightness = (0.299 * nR + 0.587 * nG + 0.114 * nB);
                if (nBrightness < 60) {
                  blackNeighborCount++;
                }
              }
            }
          }

          // 如果周围有黑色像素，则认为这个像素靠近描边，不擦除
          // 可以调整阈值，值越大保护范围越大
          if (blackNeighborCount >= 2) {
            continue;
          }

          // 擦除（设为白色）
          data[index] = 255;     // R
          data[index + 1] = 255; // G
          data[index + 2] = 255; // B
          data[index + 3] = 255; // A
        }
      }
    }
  }

  ctx.value.putImageData(imageData, 0, 0);
}

function hex2rgba(hex: string) {
  const r = parseInt(hex.slice(1, 3), 16);
  const g = parseInt(hex.slice(3, 5), 16);
  const b = parseInt(hex.slice(5, 7), 16);
  return [r, g, b, 255];
}

// 获取起始下标
const point2Index = (x: number, y: number) => {
  // 由于画布尺寸已经乘以了DPR，这里直接使用坐标计算索引
  return (Math.floor(y) * cvs.value!.width + Math.floor(x)) * 4;
};

function getColorAtPixel(cvsColors: any, x: number, y: number) {
  const index = point2Index(x, y);
  return {
    r: cvsColors[index],
    g: cvsColors[index + 1],
    b: cvsColors[index + 2],
    a: cvsColors[index + 3],
  };
}

// 改进颜色差异计算，分别检查RGBA四个通道
function diffColor(a: { r: number; g: number; b: number; a: number }, b: { r: number; g: number; b: number; a: number }) {
  // 返回每个通道的差异值数组，而不是单一的欧几里得距离
  return [
    Math.abs(a.r - b.r),
    Math.abs(a.g - b.g),
    Math.abs(a.b - b.b),
    Math.abs(a.a - b.a)
  ];
}

// 检查颜色是否在容差范围内
function isColorWithinTolerance(colorDiff: number[], tolerance: number) {
  // 检查每个通道是否都在容差范围内
  return colorDiff[0] <= tolerance &&
    colorDiff[1] <= tolerance &&
    colorDiff[2] <= tolerance &&
    colorDiff[3] <= tolerance;
}

// 检查是否为黑色边界
function isBlackBoundary(color: { r: number; g: number; b: number; a: number }) {
  // 直接检查是否为纯黑色 (0,0,0,255)
  return color.r === 0 && color.g === 0 && color.b === 0;
}

// 修改颜色
function changeColor(x: number, y: number, cvsColors: any, targetColor: any, originalColor: any) {
  // 使用队列代替递归，避免栈溢出
  const queue: number[][] = [];
  // 用于标记已经处理过的像素，避免重复处理
  const visited: Set<string> = new Set();

  // 添加起始点
  queue.push([x, y]);

  // 调整颜色相似度阈值，更精确地控制填充范围
  const colorThreshold = 160; // 可以根据实际情况调整
  // 用于存储已处理的像素，以便后续进行边缘平滑
  const processedPixels: { x: number, y: number }[] = [];

  while (queue.length > 0) {
    // 取出队列中的一个点
    const [currentX, currentY] = queue.shift()!;

    // 生成唯一的坐标键
    const key = `${currentX},${currentY}`;

    // 如果这个点已经处理过，或者超出边界，则跳过
    if (
      visited.has(key) ||
      currentX < 0 ||
      currentX >= cvs.value!.width ||
      currentY < 0 ||
      currentY >= cvs.value!.height
    ) {
      continue;
    }

    // 标记为已处理
    visited.add(key);

    // 获取当前点的颜色
    const index = point2Index(currentX, currentY);
    const curColor = {
      r: cvsColors[index],
      g: cvsColors[index + 1],
      b: cvsColors[index + 2],
      a: cvsColors[index + 3],
    };

    // 计算与原始颜色的差异（分通道）
    const colorDiff = diffColor(curColor, originalColor);

    // 检查是否为黑色边界
    if (isBlackBoundary(curColor)) {
      continue; // 跳过黑色边界
    }

    // 检查是否与目标颜色相同
    const targetColorObj = {
      r: targetColor[0],
      g: targetColor[1],
      b: targetColor[2],
      a: targetColor[3]
    };
    const targetDiff = diffColor(curColor, targetColorObj);

    // 如果当前颜色与目标颜色相同，或者与原始颜色差异太大，则跳过
    if (isColorWithinTolerance(targetDiff, 5) || !isColorWithinTolerance(colorDiff, colorThreshold)) {
      continue;
    }

    // 记录处理过的像素，用于后续边缘平滑
    processedPixels.push({ x: currentX, y: currentY });

    // 修改颜色
    cvsColors[index] = targetColor[0]; // R
    cvsColors[index + 1] = targetColor[1]; // G
    cvsColors[index + 2] = targetColor[2]; // B
    cvsColors[index + 3] = targetColor[3]; // A

    // 将相邻的点加入队列（更多方向以减少空白点）
    // 主方向
    queue.push([currentX - 1, currentY]); // 左
    queue.push([currentX + 1, currentY]); // 右
    queue.push([currentX, currentY - 1]); // 上
    queue.push([currentX, currentY + 1]); // 下

    // 对角线方向
    queue.push([currentX - 1, currentY - 1]); // 左上
    queue.push([currentX + 1, currentY - 1]); // 右上
    queue.push([currentX - 1, currentY + 1]); // 左下
    queue.push([currentX + 1, currentY + 1]); // 右下

    // 额外的中间方向，帮助填充可能被跳过的像素
    queue.push([currentX - 2, currentY]); // 左左
    queue.push([currentX + 2, currentY]); // 右右
    queue.push([currentX, currentY - 2]); // 上上
    queue.push([currentX, currentY + 2]); // 下下
  }

  // 执行第二次扫描，填充可能被遗漏的像素
  performScanlineFill(cvsColors, processedPixels, targetColor, visited);
}

// 执行扫描线填充，填补可能被遗漏的像素
function performScanlineFill(cvsColors: any, processedPixels: { x: number, y: number }[], targetColor: any, visited: Set<string>) {
  // 按行分组处理过的像素
  const pixelsByRow = new Map<number, number[]>();

  // 将像素按行分组
  for (const pixel of processedPixels) {
    if (!pixelsByRow.has(pixel.y)) {
      pixelsByRow.set(pixel.y, []);
    }
    pixelsByRow.get(pixel.y)!.push(pixel.x);
  }

  // 对每一行进行扫描线填充
  for (const [y, xValues] of pixelsByRow.entries()) {
    // 排序x坐标
    xValues.sort((a, b) => a - b);

    // 对每对相邻的x坐标之间进行填充
    for (let i = 0; i < xValues.length - 1; i++) {
      const x1 = xValues[i];
      const x2 = xValues[i + 1];

      // 如果两个点之间的距离小于某个阈值，填充它们之间的像素
      if (x2 - x1 > 1 && x2 - x1 < 5) { // 阈值可以调整
        for (let x = x1 + 1; x < x2; x++) {
          const key = `${x},${y}`;
          if (!visited.has(key)) {
            const index = point2Index(x, y);
            cvsColors[index] = targetColor[0];
            cvsColors[index + 1] = targetColor[1];
            cvsColors[index + 2] = targetColor[2];
            cvsColors[index + 3] = targetColor[3];
            visited.add(key);
          }
        }
      }
    }
  }
}

// 存储起始点颜色
let startColorInfo = {
  r: 0,
  g: 0,
  b: 0,
  a: 0
};

// 存储当前封闭区域的所有像素
let currentRegionPixels: {x: number, y: number, index: number}[] = [];

// 捕获起始点颜色并初始化填色区域
function captureStartColor(x: number, y: number) {
  if (!ctx.value) return;
  
  const imageData = ctx.value.getImageData(0, 0, cvs.value!.width, cvs.value!.height);
  const data = imageData.data;
  
  const index = point2Index(x, y);
  
  // 获取起始点的颜色
  startColorInfo = {
    r: data[index],
    g: data[index + 1],
    b: data[index + 2],
    a: data[index + 3]
  };
  
  if (isBlackBoundary(startColorInfo)) {
    // 重置颜色信息，表示无效起始点
    startColorInfo = { r: -1, g: -1, b: -1, a: -1 };
    return;
  }
  
  // 计算整个封闭区域
  calculateEnclosedRegion(x, y);
  
  // 在鼠标按下时就执行一次画笔填色
  drawWithBrush(x, y);
}

// 计算整个封闭区域
function calculateEnclosedRegion(x: number, y: number) {
  if (!ctx.value) return;
  
  const imageData = ctx.value.getImageData(0, 0, cvs.value!.width, cvs.value!.height);
  const data = imageData.data;
  
  // 颜色容差阈值
  const colorThreshold = 160;
  
  // 使用队列代替递归，避免栈溢出
  const queue: number[][] = [];
  // 用于标记已经处理过的像素，避免重复处理
  const visited: Set<string> = new Set();
  // 清空当前区域像素
  currentRegionPixels = [];

  // 添加起始点
  queue.push([x, y]);

  while (queue.length > 0) {
    // 取出队列中的一个点
    const [currentX, currentY] = queue.shift()!;

    // 生成唯一的坐标键
    const key = `${currentX},${currentY}`;

    // 如果这个点已经处理过，或者超出边界，则跳过
    if (
      visited.has(key) ||
      currentX < 0 ||
      currentX >= cvs.value!.width ||
      currentY < 0 ||
      currentY >= cvs.value!.height
    ) {
      continue;
    }

    // 标记为已处理
    visited.add(key);

    // 获取当前点的颜色
    const index = point2Index(currentX, currentY);
    const curColor = {
      r: data[index],
      g: data[index + 1],
      b: data[index + 2],
      a: data[index + 3],
    };

    // 计算与起始颜色的差异
    const colorDiff = diffColor(curColor, startColorInfo);
    
    // 检查是否为黑色边界
    if (isBlackBoundary(curColor)) {
      continue; // 跳过黑色边界
    }
    
    // 如果当前颜色与起始颜色差异太大，则跳过
    if (!isColorWithinTolerance(colorDiff, colorThreshold)) {
      continue;
    }

    // 将这个点添加到当前区域像素列表
    currentRegionPixels.push({
      x: currentX,
      y: currentY,
      index: index
    });

    // 将相邻的点加入队列（四个主方向）
    queue.push([currentX - 1, currentY]); // 左
    queue.push([currentX + 1, currentY]); // 右
    queue.push([currentX, currentY - 1]); // 上
    queue.push([currentX, currentY + 1]); // 下
    
    // 添加对角线方向，使填充更完整
    queue.push([currentX - 1, currentY - 1]); // 左上
    queue.push([currentX + 1, currentY - 1]); // 右上
    queue.push([currentX - 1, currentY + 1]); // 左下
    queue.push([currentX + 1, currentY + 1]); // 右下
  }
  
  console.log(`计算出封闭区域共有 ${currentRegionPixels.length} 个像素`);
}

// 添加画笔功能
function drawWithBrush(x: number, y: number) {
  if (!ctx.value || startColorInfo.r === -1 || currentRegionPixels.length === 0) return;
  
  const imageData = ctx.value.getImageData(0, 0, cvs.value!.width, cvs.value!.height);
  const data = imageData.data;
  
  // 获取用户选择的颜色
  const hex = userSelectedColor.value;
  const targetColor = hex2rgba(hex);
  
  // 画笔大小（半径）
  const brushSize = 15;
  
  // 存储需要上色的像素索引
  const pixelsToColor: number[] = [];

  // 遍历当前区域的所有像素
  for (const pixel of currentRegionPixels) {
    // 计算当前点到画笔中心的距离
    const distance = Math.sqrt(Math.pow(pixel.x - x, 2) + Math.pow(pixel.y - y, 2));
    
    // 如果在画笔范围内，则添加到上色列表
    if (distance <= brushSize) {
      pixelsToColor.push(pixel.index);
    }
  }

  // 对找到的所有像素进行上色
  for (const index of pixelsToColor) {
    data[index] = targetColor[0];     // R
    data[index + 1] = targetColor[1]; // G
    data[index + 2] = targetColor[2]; // B
    data[index + 3] = targetColor[3]; // A
  }
  
  // 使用requestAnimationFrame优化渲染性能
  requestAnimationFrame(() => {
    ctx.value!.putImageData(imageData, 0, 0);
  });
}

function init() {
  const img = new Image();
  img.crossOrigin = "anonymous"; // 处理跨域问题
  img.src = new URL("@/assets/images/bear.png", import.meta.url).href;

  img.onload = () => {
    // 获取设备像素比
    const dpr = window.devicePixelRatio || 1;

    // 调整canvas实际尺寸以匹配设备像素比
    cvs.value!.width = canvasWidth * dpr;
    cvs.value!.height = canvasHeight * dpr;

    // 调整显示尺寸
    cvs.value!.style.width = `${canvasWidth}px`;
    cvs.value!.style.height = `${canvasHeight}px`;

    // 缩放上下文以匹配设备像素比
    ctx.value!.scale(dpr, dpr);

    // 获取原始图片尺寸
    const originalWidth = img.width;
    const originalHeight = img.height;

    // 计算缩放比例（保持原始比例）
    let scale = 1;
    if (originalWidth > canvasWidth || originalHeight > canvasHeight) {
      const widthRatio = canvasWidth / originalWidth;
      const heightRatio = canvasHeight / originalHeight;
      scale = Math.min(widthRatio, heightRatio);
    }

    // 计算缩放后的尺寸
    const scaledWidth = originalWidth * scale;
    const scaledHeight = originalHeight * scale;

    // 计算绘制位置（居中显示）
    const x = (canvasWidth - scaledWidth) / 2;
    const y = (canvasHeight - scaledHeight) / 2;

    // 绘制图片
    ctx.value!.drawImage(img, x, y, scaledWidth, scaledHeight);
  };
}
</script>
